/**
 * 获取当前的配置信息。
 * 该函数会从页面元素中收集各项配置，包括推理模型、对话、推理参数和文本转语音等相关配置。
 *
 * @returns {Object} 返回一个包含当前所有配置信息的对象
 */
function getCurrentConfig() {
	return {
		// 推理模型相关配置
		endpoint: reasoningEndpointUrl.value,							// 推理模型API端点
		model: reasoningModelDropdown.value,							// 推理模型名称
		apiKey: reasoningEndpointKey.value,								// 推理模型API密钥
		// 嵌入模型相关配置
		embeddingEndpoint: embeddingEndpointUrl.value,					// 嵌入模型API端点
		embeddingModel: embeddingModelDropdown.value,					// 嵌入模型名称
		embeddingApiKey: embeddingEndpointKey.value,					// 嵌入模型API密钥
		// 视觉模型相关配置
		visualEndpoint: visualEndpointUrl.value,						// 视觉模型API端点
		visualModel: visualModelDropdown.value,							// 视觉模型名称
		visualApiKey: visualEndpointKey.value,							// 视觉模型API密钥
		// 对话相关配置
		systemPrompt: inputSystemPrompt.value,							// 系统提示词
		// 推理参数配置
		messageSliceLength: parseInt(messageSliceLength.value),			// 用户消息截断长度
		// 文本转语音相关配置
		apiEndpointSpeechModel: apiEndpointSpeechModel.value,			// 文本转语音API地址
		speechSpeedSlider: parseFloat(speechSpeedSlider.value),			// 文本转语音模型速度
		speechVolumeSlider: parseFloat(speechVolumeSlider.value),		// 文本转语音模型音量
		currentSpeechEngineType: currentSpeechEngineType,				// 当前文本转语音引擎类型
		speechModelVoice: speechVoiceDropdown.value,					// 语音模型的音色选择
		autoPlaySpeech: autoPlaySpeech,									// 是否自动播放语音
		disableVoiceRecognition: isDisableVoiceRecognition,				// 是否禁用语音识别自动发送
	};
};

/**
 * 将当前已保存的配置保存到服务器存储中。
 *
 * 该函数会发送一个 POST 请求到服务器的 /save 端点，将配置数据保存到指定文件。
 *
 * 若保存成功，会显示成功消息；若保存失败，会显示包含错误信息的失败消息。
 */
async function saveConfigToStorage() {
	try {
		/**
		 * 发送 POST 请求到服务器的 /save 端点，将配置数据保存到指定文件
		 */
		const response = await fetch('/save',
			{
				method: 'POST',
				// 设置请求头，指定保存的文件名并允许覆盖
				headers: {
					'X-File-Name': toBtoaString('configs.json'),
					'X-Overwrite': 'true'
				},
				// 将已保存的配置对象转换为 JSON 字符串作为请求体
				body: JSON.stringify(savedLunaraConfigs, null, '\t')
			}
		);
		// 检查响应是否成功，若不成功则抛出错误
		if (!response.ok) throw new Error('保存失败');
		// 若保存成功，显示配置保存成功的消息
		showSystemMessage("配置已保存！", "success");
	}
	catch (error) {
		// 捕获异常并显示错误信息
		showSystemMessage(`${error.name} | ${error.message} | ${error.stack}`, "error");
	}
};

/**
 * 加载指定名称的配置到用户界面
 *
 * @param {string} name - 要加载的配置名称
 */
async function loadCurrentConfig(name) {
	// 检查指定名称的配置是否存在，若不存在则直接返回
	if (!savedLunaraConfigs[name]) return;
	// 将指定名称的配置应用到用户界面
	await applyConfigToUI(savedLunaraConfigs[name]);
	// 显示配置加载成功的消息
	showSystemMessage(`配置 "${name}" 已加载`, "success");
	// 更新推理模型连接状态
	updateReasoningConnected(false);
	// 更新嵌入模型连接状态
	updateEmbeddingConnected(false);
	// 更新视觉模型连接状态
	updatevisualConnected(false);
	// 更新当前配置名称
	currentConfigName = name;
};

/**
 * 删除指定名称的配置
 *
 * @param {string} name - 要删除的配置名称
 */
async function deleteCurrentConfig(name) {
	// 弹出确认对话框，询问用户是否确定要删除指定配置，若用户取消则直接返回
	if (!confirm(`确定要删除配置 "${name}" 吗？`)) return;
	try {
		// 从已保存的配置对象中删除指定名称的配置
		delete savedLunaraConfigs[name];
		// 将更新后的配置保存到服务器存储中
		await saveConfigToStorage();
		// 重新渲染配置列表，刷新页面显示
		renderConfigList();
		// 显示配置删除成功的消息
		showSystemMessage(`配置 "${name}" 已删除`, "success");
	}
	catch (error) {
		// 捕获异常并显示错误信息
		showSystemMessage(`${error.name} | ${error.message} | ${error.stack}`, "error");
	}
};

/**
 * 删除所有已保存的配置。
 *
 * 该函数会先检查是否存在可删除的配置，若存在则弹出确认对话框，
 *
 * 确认后会向服务器发送删除请求，删除成功后清空本地配置并刷新配置列表。
 */
async function deleteAllConfigs() {
	// 检查是否存在已保存的配置，若没有则显示提示信息并终止函数
	if (Object.keys(savedLunaraConfigs).length === 0) {
		showSystemMessage("没有可删除的配置", "error");
		return;
	}
	// 弹出确认对话框，询问用户是否确定要删除所有配置
	if (confirm("确定要删除所有配置吗？此操作不可撤销！")) {
		try {
			/**
			 * 向服务器发送 DELETE 请求，删除配置文件
			 */
			const response = await fetch(`/delete/configs.json`, { method: 'DELETE' });
			// 检查响应是否成功，若不成功则抛出错误
			if (!response.ok) throw new Error('删除失败');
			// 清空本地已保存的配置对象
			savedLunaraConfigs = {};
			// 重新渲染配置列表，刷新页面显示
			renderConfigList();
			// 显示所有配置删除成功的消息
			showSystemMessage("所有配置已删除", "success");
		}
		catch (error) {
			// 捕获异常并显示错误信息
			showSystemMessage(`${error.name} | ${error.message} | ${error.stack}`, "error");
		}
	}
};

/**
 * 渲染配置列表到页面上
 *
 * 该函数会清空现有的配置列表，根据已保存的配置重新渲染。
 *
 * 如果没有保存的配置，则显示提示信息。
 *
 * 同时会为每个配置项的加载和删除按钮绑定相应的事件。
 */
function renderConfigList() {
	// 清空现有的配置列表
	configList.innerHTML = '';
	// 检查是否有保存的配置，如果没有则显示提示信息并终止函数
	if (Object.keys(savedLunaraConfigs).length === 0) {
		configList.innerHTML = '<p class="no-configs">没有保存的配置</p>';
		return;
	};
	// 遍历所有已保存的配置
	for (const name in savedLunaraConfigs) {
		/**
		 * 创建一个新的配置项元素
		 */
		const configItem = document.createElement("div");
		// 设置配置项的类名
		configItem.className = "config-item";
		// 设置配置项的内部HTML，包含配置名称和操作按钮
		configItem.innerHTML = `
            <div class="config-name">${name}</div>
            <div class="config-actions">
                <button class="config-button load-button" data-name="${name}"><i class="fas fa-file-import"></i></button>
                <button class="config-button delete-button" data-name="${name}"><i class="fas fa-trash-alt"></i></button>
            </div>
        `;
		// 将配置项添加到配置列表中
		configList.appendChild(configItem);
	};
	// 绑定 配置加载 按钮事件
	document.querySelectorAll(".load-button").forEach(button => button.addEventListener("click", async () => await loadCurrentConfig(button.dataset.name)));
	// 绑定 配置删除 按钮事件
	document.querySelectorAll(".delete-button").forEach(button => button.addEventListener("click", () => deleteCurrentConfig(button.dataset.name)))
};

/**
 * 加载已保存的配置文件。
 *
 * 尝试从服务器获取配置文件，如果获取失败则创建一个新的配置文件。
 */
async function loadSavedConfigs() {
	try {
		/** 发起网络请求，尝试获取配置文件 */
		const response = await fetch('/read/configs.json');
		// 检查响应是否成功，若失败则抛出错误
		if (!response.ok) throw new Error('无法加载配置文件');
		// 将响应的JSON数据解析并赋值给全局配置对象
		savedLunaraConfigs = await response.json();
		// 渲染配置列表到页面上
		renderConfigList();
	}
	catch (error) {
		// 捕获异常并显示错误信息
		showSystemMessage(`${error.name} | ${error.message} | ${error.stack}`, "error");
		// 清空本地已保存的配置对象
		savedLunaraConfigs = {};
		// 尝试创建一个新的配置文件
		try {
			// 尝试添加并保存一个新的配置文件
			await addAndSaveConfig('默认配置', {});
			// 显示配置文件创建成功的提示信息
			showSystemMessage('配置文件已创建', "success");
		}
		catch (saveError) {
			// 捕获异常并显示错误信息
			showSystemMessage(`${saveError.name} | ${saveError.message} | ${saveError.stack}`, "error");
		}
	}
};

/**
 * 将传入的配置应用到用户界面。
 * 如果配置中没有提供某些值，则使用默认值填充。
 *
 * @param {Object} config - 包含各项配置的对象
 */
async function applyConfigToUI(config) {
	// 合并默认配置和传入配置，传入配置中的值会覆盖默认配置中的值
	config = await mergeConfig(config);
	// 如果配置中包含系统提示词，则设置输入框的值为该提示词
	if (config.systemPrompt) inputSystemPrompt.value = config.systemPrompt;
	// 否则从文件中获取系统提示词并替换其中的技能标签
	else {
		// 从文件中获取系统提示词的 Markdown 内容
		const markdown = await fetchMarkdown('./prompt/systemPrompt.md');
		// 从文件中获取技能标签的 Markdown 内容并去除末尾空白
		const skills = (await fetchMarkdown('./prompt/functionLabel.md')).replace(/\s+$/, '');
		// 将系统提示词中的 "# Skills" 替换为技能标签内容
		const systemPrompt = markdown.replace(/# Skills/g, skills);
		// 设置输入框的值为处理后的系统提示词
		inputSystemPrompt.value = systemPrompt;
	}

	// 设置推理模型的 API 端点，若配置中未提供则使用默认值
	reasoningEndpointUrl.value = config.endpoint || "http://localhost:36789/v1";
	// 设置推理模型的 API 密钥，若配置中未提供则使用默认值
	reasoningEndpointKey.value = config.apiKey || 'key-520-1314-2000-02-18';
	/** 创建一个新的选项元素用于推理模型下拉框 */
	const reasoningModelOption = document.createElement("option");
	// 设置选项的值，若配置中未提供则使用默认值
	reasoningModelOption.value = config.model || "qwen/qwen3-coder-30b";
	// 设置选项的显示文本，若配置中未提供则使用默认值
	reasoningModelOption.textContent = config.model || "qwen/qwen3-coder-30b";
	// 将新选项添加到推理模型下拉框中
	reasoningModelDropdown.appendChild(reasoningModelOption);

	// 设置嵌入模型的 API 端点，若配置中未提供则使用默认值
	embeddingEndpointUrl.value = config.embeddingEndpoint || "http://localhost:36789/v1";
	// 设置嵌入模型的 API 密钥，若配置中未提供则使用默认值
	embeddingEndpointKey.value = config.embeddingApiKey || 'key-520-1314-2000-02-18';
	/** 创建一个新的选项元素用于嵌入模型下拉框 */
	const embeddingModelOption = document.createElement("option");
	// 设置选项的值，若配置中未提供则使用默认值
	embeddingModelOption.value = config.embeddingModel || "text-embedding-qwen3-embedding-0.6b";
	// 设置选项的显示文本，若配置中未提供则使用默认值
	embeddingModelOption.textContent = config.embeddingModel || "text-embedding-qwen3-embedding-0.6b";
	// 将新选项添加到嵌入模型下拉框中
	embeddingModelDropdown.appendChild(embeddingModelOption);

	// 设置视觉模型的 API 端点，若配置中未提供则使用默认值
	visualEndpointUrl.value = config.visualEndpoint || "http://localhost:36789/v1";
	// 设置视觉模型的 API 密钥，若配置中未提供则使用默认值
	visualEndpointKey.value = config.visualApiKey || 'key-520-1314-2000-02-18';
	/** 创建一个新的选项元素用于视觉模型下拉框 */
	const visualModelOption = document.createElement("option");
	// 设置选项的值，若配置中未提供则使用默认值
	visualModelOption.value = config.visualModel || "system-visual";
	// 设置选项的显示文本，若配置中未提供则使用默认值
	visualModelOption.textContent = config.visualModel || "system-visual";
	// 将新选项添加到视觉模型下拉框中
	visualModelDropdown.appendChild(visualModelOption);

	// 设置用户消息截断长度输入框的值，若配置中未提供则使用默认值
	messageSliceLength.value = config.messageSliceLength || 10240;
	// 设置用户消息截断长度滑块的值，若配置中未提供则使用默认值
	messageSliceLengthSlider.value = config.messageSliceLength || 10240;

	// 设置文本转语音 API 地址，若配置中未提供则使用默认值
	apiEndpointSpeechModel.value = config.apiEndpointSpeechModel || "http://localhost:36789/v1";
	// 设置文本转语音速度滑块的值，若配置中未提供则使用默认值
	speechSpeedSlider.value = config.speechSpeedSlider || 1.0;
	// 设置文本转语音音量滑块的值，若配置中未提供则使用默认值
	speechVolumeSlider.value = config.speechVolumeSlider || 1.0;
	// 若配置中包含语音模型音色选择且音色选择下拉框存在，则设置其值
	if (config.speechModelVoice && speechVoiceDropdown) speechVoiceDropdown.value = config.speechModelVoice;
	// 根据配置切换语音引擎模式
	if (config.currentSpeechEngineType === "custom") switchSpeechEngineMode("custom");
	else switchSpeechEngineMode("system");
	// 设置是否自动播放语音，若配置中未明确设置为 false 则默认为 true
	if (!config.autoPlaySpeech) {
		autoPlaySpeechButton.innerHTML = '<i class="fas fa-volume-off"></i> 启用自动朗读';
		autoPlaySpeechButton.classList.add("disable");
		autoPlaySpeech = false;
	}
	else {
		autoPlaySpeech = true;
	}
	// 根据配置设置是否禁用语音识别
	if (config.disableVoiceRecognition) {
		// 若禁用语音识别，更新按钮图标
		voiceRecognitionButton.innerHTML = '<i class="fas fa-microphone-slash"></i> 启用语音发送';
		// 添加按钮点击状态样式
		voiceRecognitionButton.classList.add("disable");
		// 设置禁用语音识别标志为 true
		isDisableVoiceRecognition = true;
	}
	else {
		// 设置禁用语音识别标志为 false
		isDisableVoiceRecognition = false;
	}
};

/**
 * 合并默认配置和传入的配置，传入配置中的值会覆盖默认配置中的值。
 * 如果无法从服务器获取默认配置文件，则直接返回传入的配置。
 *
 * @param {Object} config - 传入的配置对象
 *
 * @returns {Object} 返回合并后的配置对象
 */
async function mergeConfig(config) {
	/**
	 * 从服务器读取默认配置文件
	 */
	const response = await fetch('/read/default_config.json');
	// 判断默认配置是否存在，若不存在则直接返回传入的配置
	if (!response.ok) return config;
	/**
	 * 解析响应的 JSON 数据为默认配置对象
	 */
	const defaultConfig = response.ok ? await response.json() : {};
	// 合并默认配置和传入配置，传入配置中的值会覆盖默认配置中的值
	return { ...defaultConfig, ...config };
};

/**
 * 添加当前配置并保存到服务器
 *
 * @param {string} name - 要保存的配置名称
 */
async function addAndSaveConfig(name, newConfig) {
	/**
	 * 获取当前UI配置状态
	 */
	const config = newConfig || getCurrentConfig();
	// 将当前配置添加到已保存的配置对象中
	savedLunaraConfigs[name] = config;
	// 将当前配置保存到服务器
	await saveConfigToStorage();
	// 重新渲染配置列表到页面上
	renderConfigList();
	// 显示配置保存成功的提示信息
	showSystemMessage(`配置 "${name}" 已保存！`, "success");
	// 设置当前配置名称
	currentConfigName = name;
};